﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Drawing;

namespace MonoPhysics
{
    /// <summary>
    /// Moteur physique.
    /// </summary>
    public partial class MonoPhysics
    {
        public class EndPoint2
        {
            public EndPoint2(BoundingBox parent, float value, bool isMin, bool isX)
            {
                this.parent = parent;
                this.value = value;
                this.isMin = isMin;
                this.isX = isX;
            }
            public BoundingBox parent;
            public float value;
            public bool isMin;
            public bool isX;
        }
        public class BoundingBox
        {
            public RigidBody parent;
            LinkedListNode<EndPoint2> xMin;
            LinkedListNode<EndPoint2> yMin;
            LinkedListNode<EndPoint2> xMax;
            LinkedListNode<EndPoint2> yMax;
            public LinkedListNode<EndPoint2> XMin
            {
                get { return xMin; }
                set { xMin = value; }
            }
            public LinkedListNode<EndPoint2> YMin
            {
                get { return yMin; }
                set { yMin = value; }
            }
            public LinkedListNode<EndPoint2> XMax
            {
                get { return xMax; }
                set { xMax = value; }
            }
            public LinkedListNode<EndPoint2> YMax
            {
                get { return yMax; }
                set { yMax = value; }
            }
        }

        public delegate bool BroadPhasePredicate(RigidBody r1, RigidBody r2);
        public class BroadPhase
        {
            private List<Edge> collisionCouples = new List<Edge>();
            LinkedList<EndPoint2> llx = new LinkedList<EndPoint2>();
            LinkedList<EndPoint2> lly = new LinkedList<EndPoint2>();
            private BroadPhasePredicate canCollide;

            public BroadPhase(BroadPhasePredicate collideMinimalCondition)
            {
                this.llx = new LinkedList<EndPoint2>();
                this.lly = new LinkedList<EndPoint2>();
                this.collisionCouples = new List<Edge>();
                this.canCollide = collideMinimalCondition;
            }

            public List<Edge> GetBroadPhaseResult(float dt)
            { return this.collisionCouples; }

            public void AddItemToBroadPhase(RigidBody rb)
            {
                // On s'embete pas trop pour ce qui est des insertions, de toute smanieres elles sont plutot rares!
                this.llx.AddFirst(rb.Shape.BoundingBox.XMax);
                NotifyEndPointUpdated(rb.Shape.BoundingBox.XMax);

                this.llx.AddFirst(rb.Shape.BoundingBox.XMin);
                NotifyEndPointUpdated(rb.Shape.BoundingBox.XMin);

                this.lly.AddFirst(rb.Shape.BoundingBox.YMax);
                NotifyEndPointUpdated(rb.Shape.BoundingBox.YMax);

                this.lly.AddFirst(rb.Shape.BoundingBox.YMin);
                NotifyEndPointUpdated(rb.Shape.BoundingBox.YMin);
            }

            public void DeleteItemInBroadPhase(RigidBody rb)
            {
                this.collisionCouples.RemoveAll(delegate(Edge edge)
                { return edge.ue1 == rb || edge.ue2 == rb; });
                LinkedListNode<EndPoint2> parc = this.llx.First;
                while (parc != null)
                {
                    LinkedListNode<EndPoint2> toDelete = parc;
                    parc = parc.Next;
                    if (toDelete.Value.parent.parent == rb)
                        this.llx.Remove(toDelete);
                }
                parc = lly.First;
                while (parc != null)
                {
                    LinkedListNode<EndPoint2> toDelete = parc;
                    parc = parc.Next;
                    if (toDelete.Value.parent.parent == rb)
                        this.lly.Remove(toDelete);
                }
            }

            public void NotifyEndPointUpdated(LinkedListNode<EndPoint2> e)
            {
                bool sens;
                sens = !(e.Next == null || e.Next.Value.value > e.Value.value);
                if (sens)
                {
                    LinkedListNode<EndPoint2> parc = e.Next;
                    LinkedListNode<EndPoint2> ancetre = e;
                    if (e.Value.isMin)
                    {
                        while (parc != null && parc.Value.value <= e.Value.value) // il y aura des cas qui plantent a cause du <= ?
                        {
                            if (!parc.Value.isMin && e != parc)
                                this.RemoveCollision(e.Value, parc.Value);
                            ancetre = parc;
                            parc = parc.Next;
                        }
                    }
                    else
                    {
                        while (parc != null && parc.Value.value <= e.Value.value)
                        {
                            if (parc.Value.isMin && e != parc)
                                    this.AddCollision(e.Value, parc.Value);
                            ancetre = parc;
                            parc = parc.Next;
                        }
                    }
                    if (e != ancetre)
                    {
                        LinkedList<EndPoint2> list = e.List;
                        list.Remove(e);
                        list.AddAfter(ancetre, e);
                    }
                }
                else
                {
                    LinkedListNode<EndPoint2> parc = e.Previous;
                    LinkedListNode<EndPoint2> ancetre = e;
                    if (e.Value.isMin)
                    {
                        while (parc != null && parc.Value.value >= e.Value.value)
                        {
                            if (!parc.Value.isMin && e != parc)
                                    this.AddCollision(e.Value, parc.Value);
                            ancetre = parc;
                            parc = parc.Previous;
                        }
                    }
                    else
                    {
                        while (parc != null && parc.Value.value >= e.Value.value)
                        {
                            if (parc.Value.isMin && e != parc)
                                this.RemoveCollision(e.Value, parc.Value);
                            ancetre = parc;
                            parc = parc.Previous;
                        }
                    }
                    if (e != ancetre)
                    {
                        LinkedList<EndPoint2> list = e.List;
                        list.Remove(e);
                        list.AddBefore(ancetre, e);
                    }
                }
            }

            private void AddCollision(EndPoint2 e, EndPoint2 e2)
            {
                RigidBody r1 = e.parent.parent;
                RigidBody r2 = e2.parent.parent;
                if (!this.canCollide(r1, r2))
                    return;
                bool add = ((e.isX) ? YIntersects(r1, r2) : XIntersects(r1, r2)) &&
                !this.collisionCouples.Exists(delegate(Edge edge)
                    {
                        return (edge.ue1 == e.parent.parent && edge.ue2 == e2.parent.parent || edge.ue2 == e.parent.parent && edge.ue1 == e2.parent.parent);
                    });
                if (add)
                {
                    Edge edg = new Edge();
                    edg.ue1 = r1;
                    edg.ue2 = r2;
                    this.collisionCouples.Add(edg);
                }
            }

            private void RemoveCollision(EndPoint2 e, EndPoint2 e2)
            {
                int i = this.collisionCouples.FindIndex(delegate(Edge edge)
                {
                    return (edge.ue1 == e.parent.parent && edge.ue2 == e2.parent.parent || edge.ue2 == e.parent.parent && edge.ue1 == e2.parent.parent);
                });
                if (i >= 0)
                    this.collisionCouples.RemoveAt(i);
            }

            private bool XIntersects(RigidBody r1, RigidBody r2)
            {
                return (r1.Shape.BoundingBox.XMin.Value.value <= r2.Shape.BoundingBox.XMax.Value.value
                     && r1.Shape.BoundingBox.XMax.Value.value >= r2.Shape.BoundingBox.XMin.Value.value);
            }

            private bool YIntersects(RigidBody r1, RigidBody r2)
            {
                return (r1.Shape.BoundingBox.YMin.Value.value <= r2.Shape.BoundingBox.YMax.Value.value
                     && r1.Shape.BoundingBox.YMax.Value.value >= r2.Shape.BoundingBox.YMin.Value.value);
            }
        }
    }
}